<?php

class Model_GenericMaster extends ORM {

    public static $_CST_DDL_OPTION_WITH_PRIMARY_KEY_ID = 1;
    public static $_CST_DDL_OPTION_WITHOUT_PRIMARY_KEY_ID = 2;
    public static $_CST_OFFSET = 0;
    public static $_CST_LIMIT = 50;
    protected $db;
    protected $_db_group = 'master';
    protected $_db_group_slave = "slave";
    protected $key_not_auto = false;
    /**
     *
     * @param <type> $o_entity
     * @param <type> $s_model
     * @param <type> $n_option
     * @return <type> 
     */
    public function insert_object($o_class, $o_entity, $s_model, $n_option = 1) {
        $self = null;
        switch ($n_option) {
            case self::$_CST_DDL_OPTION_WITH_PRIMARY_KEY_ID:
                $self = $this->insert_with_tableid($o_entity, $s_model);
                break;
            case self::$_CST_DDL_OPTION_WITHOUT_PRIMARY_KEY_ID:
                $self = $this->insert_without_tableid($o_class, $o_entity, $s_model);
                break;
            default:
                break;
        }
        return $self;
    }

    /**
     * For this class, using Id in table
     * @param <type> $o_entity
     * @param <type> $s_model
     * @return <type>
     */
    private function insert_with_tableid($o_entity, $s_model) {
        
        $_table_columns = $o_entity->to_column_array();
        if ($o_entity == null || empty($_table_columns)) {
            return null;
        }

        $o_data = ORM::factory($s_model);
        $o_data->_db = Database::instance($this->_db_group_slave);
        //$this->_db = $o_data->_db;
        foreach ($_table_columns as $key => $value) {
            if ($key !== 'id') {
                $s_action = 'get' . $key;
                $o_data->$key = $o_entity->$s_action();
            }
            if ($key == 'id' && $this->key_not_auto){
                $s_action = 'get' . $key;
                $o_data->$key = $o_entity->$s_action();
            }
        }
        return $o_data->save();
    }

    /**
     * For this class, DO NOT using Id in table
     * @param <type> $o_entity
     * @param <type> $s_model
     * @return <type> 
     */
    private function insert_without_tableid($o_class, $o_entity, $s_model, $check_exsting = true) {

        $a_table_columns = $o_entity->to_column_array();
        $a_foreign_key = $o_entity->to_foreign_key_array();
        if ($o_entity == null || empty($a_table_columns) || empty($a_foreign_key)) {
            return null;
        }
        $s_foreign_key = "";
        foreach ($a_foreign_key as $key => $value) {
            $s_foreign_key = $key;
            $s_action = 'get' . $key;
            $n_data = $o_entity->$s_action();
            if (empty($n_data)) {
                return null;
            }
        }
        //check record exsting
        if ($check_exsting) {
            $o_class->_db = Database::instance($this->_db_group_slave);
            $o_data = $o_class->load($o_entity)->get_data();
            $i_foreign_key = $o_data->__get($s_foreign_key);
            if (!empty($i_foreign_key)) {
                return null;
            }
        }
        $o_data = ORM::factory($s_model);
        $o_data->_db = Database::instance($this->_db_group_slave);

        foreach ($a_table_columns as $key => $value) {
            $s_action = 'get' . $key;
            $o_data->$key = $o_entity->$s_action();
        }
        return $o_data->save();
    }

    /**
     *
     * @param <type> $o_class
     * @param <type> $o_entity
     * @param <type> $s_model
     * @param <type> $n_option
     * @return <type> 
     */
    public function update_object($o_class, $o_entity, $s_model, $n_option = 1) {
        $self = null;
        switch ($n_option) {
            case self::$_CST_DDL_OPTION_WITH_PRIMARY_KEY_ID:
                $self = $this->update_with_tableid($o_class, $o_entity, $s_model);
                break;
            case self::$_CST_DDL_OPTION_WITHOUT_PRIMARY_KEY_ID:
                $self = $this->update_without_tableid($o_class, $o_entity, $s_model);
                break;
            default:
                break;
        }
        return $self;
    }

    /**
     *
     * @param <type> $o_entity
     * @param <type> $s_model
     * @return <type>
     */
    public function update_with_tableid($o_class, $o_entity, $s_model) {
        $_table_columns = $o_entity->to_column_array();
        if ($o_entity == null || empty($_table_columns)) {
            return null;
        }

        //
        $o_data = ORM::factory($s_model, $o_entity->getId());
        $n_id = $o_data->get_data()->getId();
        if (empty($n_id)) {
            $o_data = $o_class->load($o_entity->getId());
        }
        $n_id = $o_data->get_data()->getId();
        if (empty($n_id)) {
            return null;
        }

        $o_data->_db = Database::instance($this->_db_group_slave);
        foreach ($_table_columns as $key => $value) {
            $s_action = 'get' . $key;
            $o_data->$key = $o_entity->$s_action();
        }
        return $o_data->save();
    }

    /**
     * For this class, DO NOT using Id in table
     * @param <type> $o_entity
     * @param <type> $s_model
     * @return <type>
     */
    public function update_without_tableid($o_class, $o_entity, $s_model) {
        $_table_columns = $o_entity->to_column_array();
        if ($o_entity == null || empty($_table_columns)) {
            return null;
        }

        $this->delete_without_tableid($o_class, $o_entity);

        return $this->insert_without_tableid($o_class, $o_entity, $s_model, false);
    }

    /**
     *
     * @param <type> $o_class
     * @param <type> $o_entity
     * @param <type> $n_option
     * @return <type> 
     */
    public function delete_object($o_class, $o_entity, $n_option = 1) {
        $self = null;
        switch ($n_option) {
            case self::$_CST_DDL_OPTION_WITH_PRIMARY_KEY_ID:
                $self = $this->delete_with_tableid($o_class, $o_entity);
                break;
            case self::$_CST_DDL_OPTION_WITHOUT_PRIMARY_KEY_ID:
                $self = $this->delete_without_tableid($o_class, $o_entity);
                break;
            default:
                break;
        }
        return $self;
    }

    /**
     *  For this class, using Id in table
     * @param <type> $o_class
     * @param <type> $id
     * @return <type>
     */
    public function delete_with_tableid($o_class, $id) {
        if ($id <= 0) {
            return false;
        }
        $o_class->_db = Database::instance($this->_db_group_slave);
        $o_data = $o_class->load($id);
        $n_id = $o_data->get_data()->getId();
        if (!empty($n_id)) {
            if ($o_data->delete()) {
                return true;
            }
        }
        return false;
    }

    /**
     * For this class, DO NOT using Id in table
     * @param <type> $o_class
     * @param <type> $o_entity
     * @return <type>
     */
    public function delete_without_tableid($o_class, $o_entity) {

        $_table_columns = $o_entity->to_foreign_key_array();
        if ($o_entity == null || empty($_table_columns)) {
            return null;
        }
        $o_class->_db = Database::instance($this->_db_group_slave);
        $o_data = $o_class->load($o_entity);
        $_o_delete = DB::delete($o_class->_table_name);

        foreach ($_table_columns as $key => $value) {
            $s_action = 'get' . $key;
            $n_foreign_key = $o_entity->$s_action();
            if (empty($n_foreign_key)) {
                return false;
            }
            $_o_delete->where($key, '=', $o_entity->$s_action());
        }

        if (!empty($o_data)) {

            if ($_o_delete->execute(Database::instance($this->_db_group_slave))) {
                return true;
            }
        }
        return false;
    }

    /**
     *
     * @param <type> $o_class
     * @param <type> $filter
     * @return <type> 
     */
    public function build_filter($filter) {
        $o_class = $this;
        if (!empty($filter)) {
            foreach ($filter as $key => $value) {
                if ($key === 'where open') {
                    $o_class->where_open();
                    continue;
                }
                if ($key === 'where close') {
                    $o_class->where_close();
                    continue;
                }
                if (!is_array($value)) {
                    $o_class->builder_select($key, array('where' => '='), $value);
                } else {
                    if (!empty($value)) {
                        foreach ($value as $k => $v) {
                            $a_k = explode(" ", $k);
                            $a_key = explode(" ", $key);
                            if (count($a_k) > 1 && strrpos($k, 'or ') === 0 && count($a_key) === 1) {
                                $o_class->builder_select($key, array('or where' => $a_k[1]), $v);
                            } else{
                                 $o_class->builder_select($key, array('where' => $k), $v);
                            }
                        }
                    }
                }
            }
        }
        return $o_class;
    }

    public function build_sort($sort) {
        $o_class = $this;
        if (!empty($sort) && is_array($sort)) {
            foreach ($sort as $key => $value) {
                $o_class->builder_select($key, array('order by' => $value));
            }
        }
    }

    /**
     * ex:
     * sort: array('parent_id'=>'asc')
     * filter:
     * array('name'=>array('like'=>'System')) // where `name` like '%System%'
     * or array('name'=>'a') // where `name` = 'a'
     * or array('name'=>'a','where open'=>'','id1'=>array('='=>'test'),'id2'=>array('or ='=>'test1'),'where close'=>'') // where name = 'a' and ( id1 = 'test' or id2 = 'test2' )
     * offset = null, limit = null: get count all
     * offset = 0, limit = null: get all record
     * get all data with offset and limit
     * @param <type> $offset
     * @param <type> $limit
     * @return <type>
     */
    public function load_all_object($o_class, $offset, $limit, $filter, $sort, $count) {
        $o_class->_db = Database::instance($this->_db_group);
        $o_class->clear();
        $o_class->set_column();
        $o_class->select();
        if ($offset !== null || $limit !== null) {
            $o_class->builder_select(null, array('offset' => $offset, 'limit' => $limit));
        }
        $this->build_filter( $filter);
        $this->build_sort( $sort);
        if ($count) {
            return $o_class->count_all();
        }
        $o_data = $o_class->find_all()->as_array();
        return $o_data;
    }

    /**
     * Ex:
     * for query_all: $o_data = $a_result[0]->get_data(); $o_data->getId();
     *      $o_content = $o_data->get_Content(); $a_content[0]->get_data();
     * for query: $o_data = $a_result->get_data();
     * @return Domain_Entities_Category
     */
    public function get_data_object($obj) {
        $o_class = $obj->_o_entity;
        $a_value = $obj->_object;
        // set data from `column`, alias columns of table to Object(_o_entity)
        if (!empty($a_value)) {
            foreach ($a_value as $key => $value) {
                $o_class->__set($key, $value);
            }
        }
        // set data foreign_key(Object) to Object class(_o_entity)
        $a_table = $o_class->to_table_array();
        if (!empty($a_table)) {
            foreach ($a_table as $key => $value) {
                $a_result = array();
                $s_table = substr($key, 1);
                if (!empty($obj->$s_table)) {
                    $a_result = $obj->$s_table;
                    $o_class->__set($key, $a_result);
                }
            }
        }
        return $o_class;
    }

    private function build_where($o_class, $s_column, $value, $val = null) {
        $s_search_text = $val;
        // where `column` like %text%
        if ($value === "like") {
            $s_search_text = '%' . $val . '%';
        }
        // where `column` in(x,y,z)
        if ($value === 'in') {
            $s_search_text = DB::Expr('(' . $val . ')');
        }
        // $value(=,>=,<=,in,like, is not, is)
        if ($value == "is" || $value == "is not") {
            $o_class->where($s_column, $value, null);
        } else {
            $o_class->where($s_column, $value, $s_search_text);
        }
        return $o_class;
    }

    /**
     * ....
     * ex:
     * $this->_rep->builder_select('name', array('where' => 'like'), $value); // where `name` like '%$value%', $value = 'abc' (String)
     * $this->_rep->builder_select('name', array('where' => 'in'), $value); //  where `name` in ($value), $value = 'a,b,c' (String)
     * $this->_rep->builder_select('name', array('where' => '='), $value); // where `name` = $value, $value = 1 or 'abc' (String or Integer)
     * $this->_rep->builder_select('name', array('where' => '>='), $value); // where `name` >= $value, $value = 1 or 'abc' (String or Integer)
     * $this->_rep->builder_select('name', array('where' => '<='), $value); // where `name` <= $value, $value = 1 or 'abc' (String or Integer)
     * $this->_rep->builder_select('name', array('where' => 'is'), null); // where `name` is null
     * $this->_rep->builder_select('name', array('where' => 'is'));    // where `name` is null
     * $this->_rep->builder_select('name', array('where' => 'is not'));  // where `name` is not null
     * $this->_rep->builder_select('name', array('where' => 'is not'),null); // where `name` is not null
     * $this->_rep->builder_select('name', array('limit' => 10));  // limit 10, column maybe null
     * $this->_rep->builder_select('name', array('offset' => 0));  // limit 0,10, column maybe null
     * $this->_rep->builder_select('name', array('order by' => 'asc')); // order by `name` asc
     * $this->_rep->builder_select('name', array('group by' => null)); // group by `name`
     * $this->_rep->builder_select('__alias', array('alias' => "CONCAT(`name`,' ',`id`)")); // select CONCAT(`name`,' ',`id`) as __alias
     * more
     * $this->_rep->builder_select('name', array('where' => '=','where'=>'is not','order by'=>null,'limit'=>10), $value);
     * // where `name` = $value and `name` is not null order by `name` limit 10
     *
     * @param <type> $s_column is mane of column in db
     * @param <type> $a_option is array( where,order by, group by, limit, offset )
     * @param <type> $val
     */
    public function builder_select_object($o_class, $s_column, $a_option, $val = null) {
        //$o_class->select();
        if (empty($a_option)) {
            return false;
        }
        foreach ($a_option as $key => $value) {
            switch ($key) {
                case "where":
                    $s_search_text = $val;
                    // where `column` like %text%
                    if ($value === "like" || $value === "not like") {
                        $s_search_text = "%" . $val . "%";
                    }
                    // where `column` in(x,y,z)
                    if ($value === 'in' || $value === 'not in') {
                        $s_search_text = DB::expr('(' . $val . ')');
                    }
                    // $value(=,>=,<=,in,like, is not, is)
                    if ($value == "is" || $value == "is not") {
                        $o_class->where($s_column, $value, null);
                    } else {
                        $o_class->where($s_column, $value, $s_search_text);
                    }

                    break;
                case "order by":
                    $s_sort_dir = $value;
                    if ($value == null || ($value != 'asc' && $value != 'desc')) {
                        $s_sort_dir = 'asc';
                    }
                    // oreder by `column`
                    $o_class->order_by($s_column, $s_sort_dir);
                    break;
                case "group by":
                    //group by `column`
                    $o_class->group_by($s_column);
                    break;
                case "limit":
                    // limit x
                    $limit = $value;
                    if ($limit != null && $limit > 0) {
                        $o_class->limit($limit);
                    }
                    break;
                case 'offset':
                    //limit x,y
                    $offset = $value;
                    if ($offset >= 0 && $offset != null) {
                        $o_class->offset($offset);
                    }

                    break;
                case "alias":
                    $o_class->select(DB::expr($value . " as " . $s_column));
                    break;
                case "or where":
                    $s_search_text = $val;
                    // where `column` like %text%
                    if ($value === "like" || $value === "not like") {
                        $s_search_text = "%" . $val . "%";
                    }
                    // where `column` in(x,y,z)
                    if ($value === 'in' || $value === 'not in') {
                        $s_search_text = DB::expr('(' . $val . ')');
                    }
                    // $value(=,>=,<=,in,like, is not, is)
                    if ($value == "is" || $value == "is not") {
                        $o_class->or_where($s_column, $value, null);
                    } else {
                        $o_class->or_where($s_column, $value, $s_search_text);
                    }

                    break;
            }
        }

        return $o_class;
    }

    /**
     *
     * @param <Array> $a_columns
     */
    public function set_column_object($o_class, $a_columns = array()) {
        if (empty($a_columns) || !is_array($a_columns)) {
            $o_class->_table_columns = $o_class->_o_entity->to_column_array();
        } else {
            $o_class->_table_columns = $a_columns;
        }
    }

    /**
     *
     * @param <Array> $column
     * @return <type>
     */
    public function query_all_object($o_class, $column = array()) {
        $o_class->_db = Database::instance($this->_db_group);
        $o_class->set_column($column);
        return $o_class->find_all();
    }

    /**
     *
     * @param <type> $column
     * @return <type>
     */
    public function query_object($o_class, $column = array()) {
        $o_class->_db = Database::instance($this->_db_group);
        $o_class->set_column($column);
        return $o_class->find();
    }

    /**
     *
     * @param <type> $table
     * @return <type> 
     */
    public function db_update($table = null) {
        try {
            if ($table == null) {
                $table = $this->_table_name;
            }
            return DB::update($table);
        } catch (Exception $e) {
            Log::instance()->add(Log::ERROR, 'db_update($table = null): ' . $e);
            Log::instance()->write();
            return null;
        }
    }

    /**
     *
     * @param <type> $table
     * @return <type> 
     */
    public function db_insert($table = null) {
        try {
            if ($table == null) {
                $table = $this->_table_name;
            }
            return DB::insert($table);
        } catch (Exception $e) {
            Log::instance()->add(Log::ERROR, 'db_insert($table = null): ' . $e);
            Log::instance()->write();
            return null;
        }
    }

    /**
     *
     * @param <type> $table
     * @return <type>
     */
    public function db_delete($table = null) {
        try {
            if ($table == null) {
                $table = $this->_table_name;
            }
            return DB::delete($table);
        } catch (Exception $e) {
            Log::instance()->add(Log::ERROR, 'db_delete($table = null): ' . $e);
            Log::instance()->write();
            return null;
        }
    }

    /**
     *
     * @param <type> $o_class
     * @return <type> 
     */
    public function getModel($o_class = null) {
        if (empty($o_class))
            return strtolower($this->_model);
        return strtolower($o_class->_model);
    }
    
    /**
     *
     * @param <type> $o_class
     * @return <type> 
     */
    public function getModelSelect($o_class = null) {
        $configdb = Kohana::$config->load('database');
        $mater = $this->_db_group;
        $config = $configdb->$mater;
        $database_name = $config['connection']['database'];
        if (empty($o_class))
            return strtolower($config['table_prefix'].$this->_model);
        
        return strtolower($config['table_prefix'].$o_class->_model);
    }

    /**
     *
     * @param <type> $o_class
     * @return <type> 
     */
    public function get_count_object($o_class) {
        $o_class->_db = Database::instance($this->_db_group);
        return $o_class->count_all(false);
    }

    /**
     *
     * @param <type> $obj
     * @return <type> 
     */
    public function db_execute($obj) {
        try {
            return $obj->execute(Database::instance($this->_db_group_slave));
        } catch (Exception $e) {
            Log::instance()->add(Log::ERROR, 'db_execute($obj): ' . $e);
            Log::instance()->write();
            return null;
        }
    }
    
    public function table_name() {
        $configdb = Kohana::$config->load('database');
        $mater = $this->_db_group;
        $config = $configdb->$mater;
        $database_name = $config['connection']['database'];
        return $database_name . "." . $this->_table_name;
    }
    
    public function get_table_name() {
        $configdb = Kohana::$config->load('database');
        $mater = $this->_db_group;
        $config = $configdb->$mater;
        $database_name = $config['connection']['database'];
        return $database_name . "." .$config['table_prefix']. $this->_table_name;
    }

}

?>
